/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
package alloy.parse;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import alloy.ast.QualifiedName;
import alloy.ast.QualifiedNames;

/**
 * @author Edmond Lau
 *
 * A type variable is a parameter in the moduleDecl statement,
 *   i.e. the type variable t in module collections/list[t]
 * A type argument is a parameter passed into an import statement
 *   i.e. the type argument s in open module collections/list[s] as sequence
 */

public class Environment {

	//
	// FIELDS
	//
	
	/** holds the name of this module */
	private QualifiedName _moduleName;

    /** mapping from type variables to type arguments/type variables (if recursive) */
    private Map _varsToArgs;    // QualifiedName -> QualifiedName
	
	/** A list of unbounded type arguments, in the order to be bound */
	private List _typeArgs;	// List[QualifiedName]
    
	/** whether this is a top-level or nested environment */
	private boolean _isTopLevel;
	
	//
	// CONSTRUCTORS
	//
	
	public Environment(boolean isTopLevel_) {
		this(isTopLevel_, new HashMap(), new LinkedList());
	}

	private Environment(boolean isTopLevel_, Map varsToArgs_, List typeArgs_) {
		_isTopLevel = isTopLevel_;
		_varsToArgs = new HashMap(varsToArgs_);
		_typeArgs = new LinkedList(typeArgs_);	
		_moduleName = null;
	}

	public Environment copy() {
		return new Environment(_isTopLevel, _varsToArgs, _typeArgs);
	}
	
	public Environment extend() {
		return new Environment(false, _varsToArgs, _typeArgs);
	}

	//
	// METHODS
	//

	/** Returns true if this is the top-level environment */
	public boolean isTopLevel() {
		return _isTopLevel;
	}
	
	/** 
	 * adds an argument from an import statement that needs to be bound 
	 * to the type variable in the imported module. the type variable
	 * must be fully qualified.
	 */
	public void addArgToBind(QualifiedName _typeArg) {
		checkTopLevel();
		_typeArgs.add(_typeArg);
	}

	/** 
	 * returns the number of type arguments from the import statement
	 * that need to be bound.
	 */
    public int getNumTypeArgsToBind() {
        return _typeArgs.size();
    }
    
    public QualifiedNames getFullParameterizedTypeArgs() {
    	QualifiedNames polyParams = new QualifiedNames();
    	
    	for (Iterator i = _typeArgs.iterator(); i.hasNext();) {
	    QualifiedName typeArg = (QualifiedName)i.next();
	    
	    // make a copy
	    polyParams.addQualifiedName(new QualifiedName(typeArg.getLocation(),
							  typeArg.getId(),
							  typeArg.getPath()));
    	}
	
    	return polyParams;
    }

    /** must be called before poly type args are actually bound **/
    public QualifiedNames getTypeArgs() {
    	QualifiedNames polyParams = new QualifiedNames();
    	
    	for (Iterator i = _typeArgs.iterator(); i.hasNext();) {
	    QualifiedName typeArg = (QualifiedName)i.next();
	    if (hasBinding(typeArg)) {
		typeArg = getBinding(typeArg);
	    }
	    
	    // make a copy
	    polyParams.addQualifiedName(new QualifiedName(typeArg.getLocation(),
							  typeArg.getId(),
							  typeArg.getPath()));
    	}
	
    	return polyParams;
    }
    
    /** binds a type variable to next type argument in the sequence */
    public void bindVar(QualifiedName typeVar_) {
    	checkTopLevel();
    	if(_typeArgs.size() == 0) {
    		throw new IllegalStateException("No more type arguments to bind");
    	}
    	
    	Object argToBind = _typeArgs.remove(0);
    	_varsToArgs.put(typeVar_, argToBind);
    }

	public void setName(QualifiedName moduleName_) {
		_moduleName = moduleName_;
	}
	
	public QualifiedName getName() {
		return _moduleName;
	}
	
	/** returns true if the environment has a binding for the type variable */
	public boolean hasBinding(QualifiedName typeVar_) {
		checkTopLevel();
		return _varsToArgs.containsKey(typeVar_);
	}

	/** 
	 * returns the binding for the type variable, doing recursive lookups
	 * if necessary. returns null if no such binding exists.
	 */
	public QualifiedName getBinding(QualifiedName typeVar_) {
		checkTopLevel();
		Object lookup = _varsToArgs.get(typeVar_);
		
		while(_varsToArgs.containsKey(lookup)) {
			lookup = _varsToArgs.get(lookup);
		}
		
		return (QualifiedName)lookup;
	}

    public QualifiedName getNonRecursiveBinding(QualifiedName typeVar_) {
	checkTopLevel();
        return (QualifiedName) _varsToArgs.get(typeVar_);
	
    }
	
	public String toString() {
		String ret = "    moduleName: " + _moduleName;
		ret += "    varsToArgs: " + _varsToArgs.toString();
		ret += "    argsToBind: " + _typeArgs.toString();
		return ret;
	}

	private void checkTopLevel() {
		if(_isTopLevel) throw new UnsupportedOperationException("No bindings to top-level environment");
	}
}